﻿using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;

using BrightSword.Feber.Core;

namespace BrightSword.Feber.Samples
{
    ///<summary>
    /// Implementation of Object Mappers using the Feber approach of creating type-memoized object functions and applying them on demand. 
    /// 
    /// There are several examples here of using the Feber approach to create action methods to operate on two objects of type T.
    ///</summary>
    public static class FastMapper
    {
        ///<summary>
        ///  An extension method to map all scalar properties from <paramref name="source" /> into <paramref name="_this" /> , and return <paramref
        ///   name="_this" />
        ///</summary>
        ///<param name="_this"> The object into which the values of all scalar properties of <paramref name="source" /> are to be mapped into </param>
        ///<param name="source"> The object containing the values to be mapped into <paramref name="_this" /> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> The <paramref name="_this" /> object, with all scalar properties from <paramref name="source" /> mapped onto it </returns>
        public static T MapStaticToStatic<T>(this T _this,
                                             T source)
        {
            FastMapper<T>.MapStaticToStatic(source,
                                            _this);
            return _this;
        }

        ///<summary>
        ///  An extension method to map all scalar properties from <paramref name="source" /> into <paramref name="_this" /> , and return <paramref
        ///   name="_this" />
        ///</summary>
        ///<param name="_this"> The object into which the values of all scalar properties of <paramref name="source" /> are to be mapped into </param>
        ///<param name="source"> The object containing the values to be mapped into <paramref name="_this" /> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> The <paramref name="_this" /> object, with all scalar properties from <paramref name="source" /> mapped onto it </returns>
        public static T MapDynamicToStatic<T>(this T _this,
                                              dynamic source)
        {
            FastMapper<T>.MapDynamicToStatic((object) source,
                                             _this);
            return _this;
        }

        ///<summary>
        ///  An extension method to map all scalar properties from <paramref name="source" /> into <paramref name="_this" /> , and return <paramref
        ///   name="_this" />
        ///</summary>
        ///<param name="_this"> The object into which the values of all scalar properties of <paramref name="source" /> are to be mapped into </param>
        ///<param name="source"> The object containing the values to be mapped into <paramref name="_this" /> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> The <paramref name="_this" /> object, with all scalar properties from <paramref name="source" /> mapped onto it </returns>
        public static dynamic MapStaticToDynamic<T>(this object _this,
                                                    T source)
        {
            FastMapper<T>.MapStaticToDynamic(source,
                                             _this);
            return _this;
        }

        ///<summary>
        ///  An extension method to map all scalar properties from <paramref name="source" /> into <paramref name="_this" /> , and return <paramref
        ///   name="_this" />
        ///</summary>
        ///<param name="_this"> The object into which the values of all scalar properties of <paramref name="source" /> are to be mapped into </param>
        ///<param name="source"> The object containing the values to be mapped into <paramref name="_this" /> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> The <paramref name="_this" /> object, with all scalar properties from <paramref name="source" /> mapped onto it </returns>
        public static dynamic MapDynamicToDynamic<T>(this object _this,
                                                     dynamic source)
        {
            FastMapper<T>.MapDynamicToDynamic((object) source,
                                              _this);
            return _this;
        }

        /// <summary>
        /// An extension method to map all scalar properties from <paramref name="source" /> into private backing fields of <paramref name="_this" />,
        /// and return <paramref name="_this" />
        /// </summary>
        /// <param name="_this"></param>
        /// <param name="source"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static dynamic MapToBackingFields<T>(this T _this,
                                                    dynamic source)
        {
            FastMapper<T>.MapToBackingFields((object) source,
                                             _this);
            return _this;
        }
    }

    public static class FastMapper<T>
    {
        private static readonly StaticToStaticMapperBuilder _staticToStaticMapper = new StaticToStaticMapperBuilder();
        private static readonly DynamicToDynamicMapperBuilder _dynamicToDynamicMapper = new DynamicToDynamicMapperBuilder();
        private static readonly StaticToDynamicMapperBuilder _staticToDynamicMapper = new StaticToDynamicMapperBuilder();
        private static readonly DynamicToStaticMapperBuilder _dynamicToStaticMapper = new DynamicToStaticMapperBuilder();
        private static readonly DynamicToStaticBackingFieldsMapperBuilder _dynamicToStaticBackingFieldsMapper = new DynamicToStaticBackingFieldsMapperBuilder();

        /// <summary>
        ///   Maps all the properties defined in <typeparamref name="T" /> from a dynamic source to a dynamic destination
        /// </summary>
        /// <param name="source"> The source from which to map values </param>
        /// <param name="destination"> The destination to which to map values </param>
        public static void MapDynamicToDynamic(dynamic source,
                                               dynamic destination)
        {
            _dynamicToDynamicMapper.Action((object) destination,
                                           (object) source);
        }

        /// <summary>
        ///   Maps all the properties defined in <typeparamref name="T" /> from a static-typed source to a static-typed destination
        /// </summary>
        /// <param name="source"> The source from which to map values </param>
        /// <param name="destination"> The destination to which to map values </param>
        public static void MapStaticToStatic(T source,
                                             T destination)
        {
            _staticToStaticMapper.Action(destination,
                                         source);
        }

        /// <summary>
        ///   Maps all the properties defined in <typeparamref name="T" /> from a static-typed source to a dynamic destination
        /// </summary>
        /// <param name="source"> The source from which to map values </param>
        /// <param name="destination"> The destination to which to map values </param>
        public static void MapStaticToDynamic(T source,
                                              dynamic destination)
        {
            _staticToDynamicMapper.Action((object) destination,
                                          source);
        }

        /// <summary>
        ///   Maps all the properties defined in <typeparamref name="T" /> from a dynamic source to a static-typed destination
        /// </summary>
        /// <param name="source"> The source from which to map values </param>
        /// <param name="destination"> The destination to which to map values </param>
        public static void MapDynamicToStatic(dynamic source,
                                              T destination)
        {
            _dynamicToStaticMapper.Action(destination,
                                          (object) source);
        }

        /// <summary>
        ///   Maps all the properties defined in <typeparamref name="T" /> from a dynamic source to the non-public backing fields of a static-typed destination This implementation assumes that the convention for naming the backing field for a property of name 'PropertyName' is '_propertyName' The mapper does not throw any exceptions if the backing fields are not present or are not named properly. It simply skips that property.
        /// </summary>
        /// <param name="source"> The source from which to map values </param>
        /// <param name="destination"> The destination to which to map values </param>
        public static void MapToBackingFields(dynamic source,
                                              T destination)
        {
            _dynamicToStaticBackingFieldsMapper.Action(destination,
                                                       (object) source);
        }

        private class DynamicToDynamicMapperBuilder : ActionBuilder<T, object, object>
        {
            protected override Expression PropertyExpression(PropertyInfo property,
                                                             ParameterExpression leftInstanceParameterExpression,
                                                             ParameterExpression rightInstanceParameterExpression)
            {
                return rightInstanceParameterExpression.GetDynamicPropertyMutatorExpression<T>(property,
                                                                                               leftInstanceParameterExpression.GetDynamicPropertyAccessorExpression<T>(property));
            }
        }

        private class DynamicToStaticBackingFieldsMapperBuilder : ActionBuilder<T, T, object>
        {
            protected override Expression PropertyExpression(PropertyInfo property,
                                                             ParameterExpression leftInstanceParameterExpression,
                                                             ParameterExpression rightInstanceParameterExpression)
            {
                var fieldName = String.Format("_{0}{1}",
                                              property.Name.Substring(0,
                                                                      1)
                                                      .ToLower(),
                                              property.Name.Substring(1));

                var fieldInfo = typeof (T).GetField(fieldName,
                                                    BindingFlags.Instance | BindingFlags.NonPublic);

                Debug.Assert(fieldInfo != null);

                var destinationAccessExpression = Expression.Field(leftInstanceParameterExpression,
                                                                   fieldInfo);

                return Expression.Assign(destinationAccessExpression,
                                         rightInstanceParameterExpression.GetDynamicPropertyAccessorExpression<T>(property));
            }
        }

        private class DynamicToStaticMapperBuilder : ActionBuilder<T, T, object>
        {
            protected override Expression PropertyExpression(PropertyInfo property,
                                                             ParameterExpression leftInstanceParameterExpression,
                                                             ParameterExpression rightInstanceParameterExpression)
            {
                return Expression.Assign(Expression.Property(leftInstanceParameterExpression,
                                                             property),
                                         rightInstanceParameterExpression.GetDynamicPropertyAccessorExpression<T>(property));
            }
        }

        private class StaticToDynamicMapperBuilder : ActionBuilder<T, object, T>
        {
            protected override Expression PropertyExpression(PropertyInfo property,
                                                             ParameterExpression leftInstanceParameterExpression,
                                                             ParameterExpression rightInstanceParameterExpression)
            {
                return leftInstanceParameterExpression.GetDynamicPropertyMutatorExpression<T>(property,
                                                                                              Expression.Property(rightInstanceParameterExpression,
                                                                                                                  property));
            }
        }

        private class StaticToStaticMapperBuilder : ActionBuilder<T, T, T>
        {
            protected override Expression PropertyExpression(PropertyInfo property,
                                                             ParameterExpression leftInstanceParameterExpression,
                                                             ParameterExpression rightInstanceParameterExpression)
            {
                return Expression.Assign(Expression.Property(leftInstanceParameterExpression,
                                                             property),
                                         Expression.Property(rightInstanceParameterExpression,
                                                             property));
            }
        }
    }
}